/**A calculator class that evaluates expressions
 * 
 * @author locphan
 * @version 1.01
 */

import java.util.EmptyStackException;

public class Calculator
{
   private final String  VALID_SYMBOLS = "()1234567890^/*+-\t ";
   private final String VALID_NUMBERS = "1234567890";
   private final String OPS = "^/*+-()";
   private String infix;
   private String postfixstring;
   
   private ArrayStack<Character> opstack;
   private StringBuilder postfix = new StringBuilder();
   private ArrayStack<Double> operandstack;
   private ArrayStack<Character> parenthesis;


   /**Constructs a calculator 
    * 
    */
   public Calculator()
   {
      infix = "";
      postfixstring = "";
      opstack = new ArrayStack<Character>();
      operandstack = new ArrayStack<Double>();
      parenthesis = new ArrayStack<Character>();
   }
   
   /**Returns calculated result 
    * 
    * @return result
    */
   public double result()
   {
      double answer = 0;
      String[] tokens = postfixstring.split(" ");
     
         for(int i=0;i<tokens.length;i++)
         {
            String nextToken = tokens[i];
            if(nextToken.isEmpty())
            {
               
            }
            else if(Character.isDigit(nextToken.charAt(0)))
            {              
               double value = Double.parseDouble(nextToken);
               operandstack.push(value);
            }
            else if(isOp(nextToken.charAt(0)))
            {
               double result = evaluate(nextToken.charAt(0));
               operandstack.push(result);
            }
            
         }
         if(!opstack.empty())
         {
            System.out.println(opstack.peek());
         }
         answer = operandstack.pop();
         
      
      return answer;
   }
   
   /**Evaluate operands
    * 
    * @param op Operator
    * @return Result
    */
   private double evaluate(char op)
   {
      double right;
      double left;
      double result = 0;
      if(op=='^')
      {
         double number = operandstack.pop();
         number = 0-number;
         result = number;
      }
      else
      {
         right = operandstack.pop();
         left = operandstack.pop();
         switch(op)
         {
            case '+' : result = left+right; break;
            case '-' : result = left-right; break;
            case '*' : result = left*right; break;
            case '/' : result = left/right; break;        
         }
      }
      return result;
   }
   
   /**Converts expression from infix to post fix
    * 
    * @param expression Infix Expression
    * @return Postfix Expression
    * @throws IllegalStateException thrown when there is an syntax error
    * @throws EmptyStackException thrown when there is a missing parenthesis
    */
   public String toPostFix(String expression) throws IllegalStateException, EmptyStackException
   {
      infix = preProcess(expression);
      postfix = new StringBuilder();
      
         for(int i=0;i<infix.length();i++)
         {
            char token = infix.charAt(i);
            
            if(token=='(')
            {
               parenthesis.push(token);
            }
            if(token==')')
            {
               parenthesis.pop();
            }

            if(VALID_NUMBERS.indexOf(token)>=0)
            {
               postfix.append(token);
            }
            else if(isOp(token))
            {
               processOp(token);
               postfix.append(' ');
            }
                           
         }
         while(!opstack.empty())
         {
            char op = opstack.pop();
            if(op=='(')
            {
               
            }
            postfix.append(' ');
            postfix.append(op);
            
         }
      
      
      postfixstring = postfix.toString().trim().replaceAll("  ", " ").replaceAll("   ", " ");
      return postfixstring;
      
   }
   
   /**Checks if a character is an operator
    * 
    * @param op Operator
    * @return True is character is an operator
    *           false otherwise
    */
   private boolean isOp(char op)
   {
      return OPS.indexOf(op)!=-1;
   }
   
   /**Process operator
    * 
    * @param op Operator
    */
   private void processOp(char op)
   {
      if(opstack.empty() || op=='(')
      {
         opstack.push(op);
      }
      else
      {
         char topop = opstack.peek();
         if(precedence(op)>precedence(topop))
         {
            opstack.push(op);
         }
         else
         {
            while(!opstack.empty() && precedence(op) <= precedence(topop))
            {
               opstack.pop();
               if(topop=='(')
               {
                  break;
               }
               postfix.append(' ');
               postfix.append(topop);
               if(!opstack.empty())
               {
                  topop=opstack.peek();
               }
            }
            if(op!=')')
               opstack.push(op);
         }
      }
   }
   
   /**Checks precedence of an operator
    * 
    * @param token operator
    * @return precedence
    */
   private int precedence(char token)
   {
      int result = 0;
      if(token=='*' || token=='/')
      {
         result = 2;
      }
      else if(token=='+'||token=='-')
      {
         result = 1;
      }
      else if(token=='^')
         result = 3;
      return result;
   }
   
   /**Processes an expression
    * 
    * @param rawexpression raw expression
    * @return preprocessed expression
    * @throws IllegalStateException thrown when expression is invalid
    */
   public String preProcess(String rawexpression) throws IllegalStateException
   {        
      StringBuffer buffer = new StringBuffer();
      rawexpression = rawexpression.replaceAll(" ", "");
      for(int i=0;i<rawexpression.length();i++)
      {
         char token = rawexpression.charAt(i);
         char nexttoken = 0;
         boolean lastchar = false;
         boolean isTokenDigit = false;
         boolean isNextTokenDigit = false;;
         
         if(i==rawexpression.length()-1)
         {
            lastchar = true;     
         }
         else
         {
            nexttoken = rawexpression.charAt(i+1);
         }
         
         if(VALID_SYMBOLS.indexOf(token) < 0)
         {
            throw new IllegalStateException("Syntax Error!");
         }
         
         if(!lastchar)
         {
            isTokenDigit = Character.isDigit(token);
            isNextTokenDigit = Character.isDigit(nexttoken);
            if((isTokenDigit && nexttoken=='(')
                  || (token==')' && isNextTokenDigit)
                  || (token==')') && (nexttoken=='(')
                  || (token==')' && nexttoken=='^'))
            {
               buffer.append(token+"*"+nexttoken);
               i++;
               
            }
            else
            {
               buffer.append(token);
            }
         }
         else if(lastchar)
         {
            buffer.append(token);
         }  
      }
      infix = buffer.toString();
      return infix;     
   }
   
}
